#pragma once 

#include <iostream>
#include <memory>
#include <queue>
#include <unistd.h>
#include <cstdlib>
#include <pthread.h>
#include <cassert>
#include <sys/prctl.h>

size_t g_threadnum = 5; // 默认的线程数

using namespace std;

template <class T>
class ThreadPool
{
private: // 不允许手动创建实例
    ThreadPool(size_t num = g_threadnum)
        : threadnum_(num)
        , isStarted_(false)
        , taskq_()
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ThreadPool(const ThreadPool<T>&) = delete; // 拷贝构造取消
    void operator=(const ThreadPool<T>&) = delete;  // 赋值 取消
public:
    // 一个类只能创建一个对象，所以这个创建实例的函数也需要独立于对象
    static ThreadPool<T>* getInstance()
    {
        static pthread_mutex_t curlock;
        // 过滤重复的条件判断
        if (nullptr == inst)
        {
            pthread_mutex_init(&curlock, nullptr);
            pthread_mutex_lock(&curlock);
            if (nullptr == inst)
            {
                inst = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&curlock);
            pthread_mutex_destroy(&curlock);
        }
        return inst;
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    // 执行任务
    // 因为 这个函数 的接收参数是 void* ，并且只有一个参数，但是这个函数作为类内函数的话，那就默认带了一个参数，而这个函数本来只能由一个参数
    // 所以这里需要设定为静态函数，但是后续可能会用到类内的数据，所以类内函数使用这个函数的时候可以传一个 this 进来
    static void* dotask(void* args)
    {
        // 不打算等待线程了，让线程自己分离
        pthread_detach(pthread_self());
        ThreadPool<T>* pool = static_cast<ThreadPool<T>*>(args);
        while (1)
        {
            pool->lockQueue();
            while (!pool->hasTask()) // 如果没有任务，那么就让当前线程去等待
            {
                pool->waitForTask();
            }
            // 有任务，拿出来
            T task = pool->pop();
            pool->unlockQueue(); // 线程池的锁释放

            task.run(); // 直接让指定的线程执行这个任务
        }
    }

    // 启动线程池
    void start()
    {
        // 不允许重复启动
        assert(!isStarted_);
        for (int i = 0; i < threadnum_; i ++) 
        {
            pthread_t thread;
            pthread_create(&thread, nullptr, dotask, this);
        }

        isStarted_ = true;
    }

    // 添加任务并尝试马上完成这个任务
    void push(const T& todo)
    {
        lockQueue();
        taskq_.push(todo);
        // 分配线程去完成任务
        chooseToDoTask();
        unlockQueue();
    }

    int threadCount()
    { return threadnum_; }

private:
    // 获取任务
    T pop()
    {
        T content = taskq_.front();
        taskq_.pop();
        return content;
    }

    // 等待任务
    void waitForTask()              { pthread_cond_wait(&cond_, &mutex_); }

    // 判断是否有任务
    bool hasTask()                  { return !taskq_.empty(); }
    // 选择线程去执行任务, 也就是唤醒线程
    void chooseToDoTask()           { pthread_cond_signal(&cond_); }

    void lockQueue()                { pthread_mutex_lock(&mutex_); }

    void unlockQueue()              { pthread_mutex_unlock(&mutex_); }

private:
    bool isStarted_; // 是否启动
    size_t threadnum_; // 线程的个数
    queue<T> taskq_;   // 任务队列
    pthread_mutex_t mutex_; // 锁
    // 条件变量就是用来 排队的，没任务的时候进行排队, 所以可以使用 条件变量 这个特点
    // 对等待的线程进行排队
    pthread_cond_t cond_;   

    // 将这个线程池设计成懒汉模式
    static ThreadPool<T>* inst;
};

// 在类外部定义这个实例
template<class T>
ThreadPool<T>* ThreadPool<T>::inst = nullptr;